#pragma once

#include "../commons/utils.hpp"
#include "../commons/constants.hpp"
#include "../commons/code_generator.hpp"
#include "../lexer/lexercc.hpp"
#include "my_parser.hpp"
#include <iostream>
#include <fstream>
#include <filesystem>

using std::fstream;
namespace fs = std::filesystem;

static void toInitList(ostream &oss, const MyParser::RuleType &rule){
    oss << "{";
    toInitList(oss, rule.name);
    oss << ", " << rule.index << ", " << rule.lhs << ", " << rule.rhs.size();
    oss << "}";
}

static void toInitList(ostream &oss, const MyParser::Action &action){
    oss << "{";
    switch(action.atype){
    case MyParser::ActionType::ACCEPT:
        oss << "true";
        break;
    case MyParser::ActionType::SHIFT:
        oss << action.state;
        break;
    case MyParser::ActionType::REDUCE:
        toInitList(oss, action.rule);
        break;
    default: // UNDEFINED
        break;
    }
    oss << "}";
}

static void writeParserHeader(ostream &oss) {
    oss << "/**\n * Parser generated by MyParser parser generator V1.0.0\n"
           " * Created at "
        << __DATE__ << " "
        << __TIME__ "\n"
                    " * Built by Zeege (R) with ♥\n"
                    " * See https://gitee.com/zeege/my-lex for details\n**/\n\n";
    oss << "#include \"parser_typedef_min.hpp\"\n";
    oss << "#include \"lexer.cpp\"\n";
    oss << "#include <iostream>\n";
    oss << "#include <iomanip>\n";
    oss << "#include <fstream>\n\n";
    oss << "using namespace std;\n\n";
    oss << "MyParser buildParser();\n\n";
}

static void writeParserDemo(ostream &oss) {
    oss << "/**\n * This is a demo for using the parser\n * Happy parsing!\n */\n";
    oss << "int main() {\n";
    oss << "\tconst MyLexer lexer = buildLexer();\n";
    oss << "\tMyParser parser = buildParser();\n";
    oss << "\tfstream sourceIn(/* path to source code to parse */);\n";
    oss << "\tfstream astConfigIn(/* path to AST config*/);\n";
    oss << "\tfstream astOut(/* path to AST output */);\n";
    oss << "\tif(!sourceIn.is_open() || !astConfigIn.is_open() || !astOut.is_open()){\n";
    oss << "\t\tcout<<\"Cannot open file.\\n\";\n\n\t\treturn 0;\n\t}\n";
    oss << "\tvector<Token> tokens = lexer.lex(sourceIn);\n";
    oss << "\tMyParser::PtrParseTreeNode parseTree = parser.parse(tokens);\n";
    oss << "\tASTConfig config = ASTConfig::configWithJson(astConfigIn);\n";
    oss << "\tPtrASTNode ast = ASTNode::buildAST(parseTree, config);\n";
    oss << "\t// You may also print AST in txt format, use: ast->printAST(astOut);\n";
    oss << "\tastOut << std::setw(2) << json_wrap(ast) << endl;\n";
    oss << "\treturn 0;\n}\n\n";
}

static void writeParserInternal(ostream &oss, const vector<string> &symbols,
                                const vector<vector<MyParser::Action>> &parseTab) {
    oss << "MyParser buildParser() {\n";
    oss << "\tconst vector<string> _symbols = ";
    toInitList(oss, symbols);
    oss << ";\n";
    oss << "\tconst vector<vector<MyParser::Action>> _parseTable = ";
    toInitList(oss, parseTab);
    oss << ";\n";
    oss << "\treturn MyParser(_symbols, _parseTable);\n}";
}

void writeParser(const MyParser &parser, const MyLexer &lexer, const string& pathname){
    writeLexer(lexer, pathname, "../lexer/", false);
    cout << "[Info] Start generating parser, wait patiently...\n";
    fs::path pp(pathname);
    if (!fs::exists(pp)) {
        fs::create_directory(pp);
    }
    fs::remove(pathname + "/parser_typedef_min.hpp");
    fs::remove(pathname + "/parser.cpp");
    fs::copy_file("parser_typedef_min.hpp", pathname + "/parser_typedef_min.hpp");
    fstream fss;
    __OPEN_AND_CHECK(fss, pathname + "/parser.cpp", true);
    writeParserHeader(fss);
    writeParserDemo(fss);
    writeParserInternal(fss, parser.getSymbols(), parser.getParseTable());
    fss.close();
    cout << "[Info] Parser created at " << pathname << "/parser.cpp\n\n";
}
